#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import time
import uuid

from common.const import ReportDBLabel, DBLogLevel

from adds.comm.const import LogLevel
from common.common_models import LogDetail
from exchange import log
from exchange.commons.common import check_win_path_legality, read_file
from exchange.commons.const import ExchangeJobInfo, ExchangeRestoreLocation, RepositoryDataTypeEnum, BodyErr, \
    ExchangeReportDBLabel, ProgressConst, SubJobStatusEnum, CopyDataTypeEnum
from exchange.commons.exchange_function_tool import log_start_end, out_result_with_job_info, \
    progress_notify_with_job_info
from exchange.restore.exchange_restore_base import ExchangeRestoreBase
from exchange.restore.restore_param_parser import RestoreParamParser


# Exchange备份前置任务基类
class ExchangeRestorePrePare(ExchangeRestoreBase):
    def __init__(self, p_id, job_id, sub_job_id, json_param):
        super().__init__(p_id, job_id, sub_job_id, json_param)
        self._pid = p_id
        self._param = json_param

    @log_start_end()
    @out_result_with_job_info()
    @progress_notify_with_job_info()
    def do_prepare_task(self, job_info: ExchangeJobInfo):
        # 检查路径合法性
        path_legally = self.check_target_edb_and_log_path_legality()
        if not path_legally:
            log.error("path is illegally")
            return False
        # 检查目标环境exchange server版本是否一致(版本必须完全一致)
        expect_version = RestoreParamParser.get_exchange_server_version(self._json_param)
        version_consistency, target_version = self._check_exchange_version_consistency(expect_version)
        log.info(f"expect_version:{expect_version}, target_version: {target_version},"
                 f" version_consistency :{version_consistency}")
        if not version_consistency:
            log.info(f"version is inconsistency expect_version:{expect_version}, target_version: {target_version}")
            log_detail = LogDetail(logInfo=ReportDBLabel.PRE_REQUISIT_FAILED,
                                   logDetail=BodyErr.EXCHANGE_IS_INCONSISTENT.value,
                                   logDetailParam=[str(version_consistency), str(target_version)],
                                   logTimestamp=int(time.time()),
                                   logLevel=DBLogLevel.ERROR)
            self._report_progress(SubJobStatusEnum.FAILED.value, ProgressConst.PROGRESS_ONE_HUNDRED, [log_detail])
            return False

        # 检查/创建同名数据库
        for database_name_map in RestoreParamParser.get_database_name_maps(self._param):
            source_database_name = database_name_map.get("source_db_name", "")
            target_database_name = database_name_map.get("source_target_name", "")
            server_name = self._check_exchange_database_server_name(target_database_name)
            if server_name:
                log.info(f"database exist :{target_database_name}")
                # 判断数据库所在节点是否在同一个dag或者单机中
                if not self.check_database_server(server_name):
                    error_detail = LogDetail(logInfo=ExchangeReportDBLabel.DATABASE_CREATE_FAILED,
                                             logInfoParam=[target_database_name], logLevel=LogLevel.ERROR.value,
                                             logDetail=BodyErr.DATABASE_EXIST_IN_OTHER_SERVER.value,
                                             logDetailParam=[target_database_name])
                    self._report_progress(SubJobStatusEnum.FAILED.value, ProgressConst.PROGRESS_ONE_HUNDRED,
                                          [error_detail])
                    return False
                # 同名数据库存在，判断自定义路径和为同名数据库父路径
                if self.check_contains_edb_and_log_path(target_database_name):
                    continue
                # 恢复目标路径检查，不一致上报目标恢复路径不一致需要返回错误码
                log.error(f"target edb path or log path is not contains target database:{target_database_name} path!")
                error_detail = LogDetail(logInfo=ExchangeReportDBLabel.DATABASE_CREATE_FAILED,
                                         logInfoParam=[target_database_name], logLevel=LogLevel.ERROR.value,
                                         logDetail=BodyErr.EDB_AND_LOG_PATH_SAME_CHECK_FAILED.value,
                                         logDetailParam=[target_database_name])
                self._report_progress(SubJobStatusEnum.FAILED.value, ProgressConst.PROGRESS_ONE_HUNDRED, [error_detail])
                return False

            # 同名数据库不存在场景
            target_edb_path = RestoreParamParser.get_target_database_edb_path(self._param)
            target_log_path = RestoreParamParser.get_target_database_log_path(self._param)
            if len(target_edb_path) == 0 and len(target_log_path) == 0:
                # 原位置改名恢复场景，同名数据库不存在，并且未配置自定义路径
                # 获取副本备份时edb和log路径，在同级创建restore_dbname_uuid文件夹执行恢复
                try:
                    new_edb_path, new_log_path = self.get_new_edb_and_log_path_from_meta(source_database_name,
                                                                                         target_database_name)
                except Exception as err:
                    log.error(f"Get copy meta data info error, err: {err}")
                    log_detail = LogDetail(logInfo=ReportDBLabel.PRE_REQUISIT_FAILED,
                                           logDetail=BodyErr.COPY_META_DATA_NOT_EXIST.value,
                                           logTimestamp=int(time.time()),
                                           logLevel=DBLogLevel.ERROR)
                    self._report_progress(SubJobStatusEnum.FAILED.value, ProgressConst.PROGRESS_ONE_HUNDRED,
                                          [log_detail])
                    return False
                # 获取原位置所在节点
                server_name = self.get_server_from_meta(source_database_name)
                is_created = self._create_exchange_database(target_database_name, server_name, new_edb_path,
                                                            new_log_path)
                if is_created:
                    self._report_running_progress(ExchangeReportDBLabel.DATABASE_CREATE_SUCCESS, [target_database_name],
                                                  ProgressConst.MIN_PROGRESS)
                    continue
                log.error(f"database create fail:{target_database_name}!")
                self._report_failed_progress(ExchangeReportDBLabel.DATABASE_CREATE_FAILED, [target_database_name],
                                             ProgressConst.MIN_PROGRESS)
                return False
            # 创建同名数据库执行恢复，根据目标edb路径和log路径(必填)
            # 查询所有活动数据库副本和所属服务节点,分组统计数量，获取最少活动副本节点(shell命令)
            # 创建活动数据库，指定服务节点(shell命令)
            new_edb_path, new_log_path = self.get_new_edb_and_log_path_from_param(target_database_name)
            server_name = self._get_idle_node_server()
            is_created = self._create_exchange_database(target_database_name, server_name, new_edb_path,
                                                        new_log_path)
            if is_created:
                self._report_running_progress(ExchangeReportDBLabel.DATABASE_CREATE_SUCCESS, [target_database_name],
                                              ProgressConst.MIN_PROGRESS)
                continue
            log.error(f"database create fail:{target_database_name}!")
            self._report_failed_progress(ExchangeReportDBLabel.DATABASE_CREATE_FAILED, [target_database_name],
                                         ProgressConst.MIN_PROGRESS)
            return False
        log.info("all database created")
        return True

    def check_database_server(self, server_name):
        nodes_list = RestoreParamParser.get_restore_nodes_list(self._json_param)
        for node in nodes_list:
            if server_name.lower() == node.get("name").lower():
                log.info("find node do DAG database restore")
                return True
        return False

    def check_target_edb_and_log_path_legality(self):
        # 检查是否应该设置edb和log参数
        target_edb_path = RestoreParamParser.get_target_database_edb_path(self._param)
        target_log_path = RestoreParamParser.get_target_database_log_path(self._param)
        restore_location = RestoreParamParser.get_restore_location(self._param)
        if ExchangeRestoreLocation.ORIGIN == restore_location:
            # 自定义路径不允许存在
            if target_edb_path or target_log_path:
                log.error("origin restore but target path set")
                return False
            return True
        if ExchangeRestoreLocation.NEW == restore_location:
            # 新位置两个路径必须存在
            if not all([target_edb_path, target_log_path]):
                log.error("new restore but target path not all set")
                return False
            return check_win_path_legality(target_edb_path) and check_win_path_legality(target_log_path)
        return True

    def get_new_edb_and_log_path_from_meta(self, source_database_name, target_database_name):
        # C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\mb01\\mb01.edb
        # C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\mb01
        # 转换成
        # C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\Restore_mb01_uuid\\Restore_mb01_uuid.edb
        # C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\Restore_mb01_uuid
        # 从备份元数据文件，取出原数据库信息
        copy_meta_data = RestoreParamParser.get_copy_meta_data_info(source_database_name, self._param)
        edb_path = copy_meta_data.get("extendInfo", "").get("edb_path", "")
        log_path = copy_meta_data.get("extendInfo", "").get("log_path", "")
        second_last_backslash_index = edb_path.rfind('\\', 0, edb_path.rfind('\\'))
        edb_pre_path = edb_path[:second_last_backslash_index]
        log_pre_path = log_path[:log_path.rfind('\\')]
        new_db_file_name = "Restore_" + target_database_name + "_" + str(uuid.uuid4())[:8]
        new_edb_path = edb_pre_path + '\\' + new_db_file_name + '\\' + new_db_file_name + '.edb'
        new_log_path = log_pre_path + '\\' + new_db_file_name
        return new_edb_path, new_log_path

    def get_new_edb_and_log_path_from_param(self, target_database_name):
        """
        C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\
        C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\
        转换成
        C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\target_database_name\\target_database_name.edb
        C:\\Program Files\\Microsoft\\Exchange Server\\V15\\Mailbox\\target_database_name
        从备份元数据文件，取出原数据库信息
        :param target_database_name:
        :return:
        """
        target_edb_path = RestoreParamParser.get_target_database_edb_path(self._param)
        target_log_path = RestoreParamParser.get_target_database_log_path(self._param)
        new_edb_path = target_edb_path + '\\' + target_database_name + '\\' + target_database_name + '.edb'
        new_log_path = target_log_path + '\\' + target_database_name
        return new_edb_path, new_log_path

    def get_server_from_meta(self, source_database_name):
        copy_meta_data = RestoreParamParser.get_copy_meta_data_info(source_database_name, self._param)
        return copy_meta_data.get("extendInfo", "").get("server_name", "")

    def check_contains_edb_and_log_path(self, target_database_name):
        target_edb_path = RestoreParamParser.get_target_database_edb_path(self._param)
        target_log_path = RestoreParamParser.get_target_database_log_path(self._param)
        if all([target_edb_path, target_log_path]):
            edb_path, log_path = self._get_edb_and_log_path(target_database_name)
            return target_edb_path in edb_path and target_log_path in log_path
        return True
